import {useCallback, useMemo, useState} from 'react';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import {useSearchContext} from '@components/Search/SearchContext';
import {unholdRequest} from '@libs/actions/IOU';
import {setupMergeTransactionData} from '@libs/actions/MergeTransaction';
import {exportReportToCSV} from '@libs/actions/Report';
import {getExportTemplates} from '@libs/actions/Search';
import Navigation from '@libs/Navigation/Navigation';
import {getIOUActionForTransactionID, getReportAction, isDeletedAction} from '@libs/ReportActionsUtils';
import {isMergeAction} from '@libs/ReportSecondaryActionUtils';
import {
    canDeleteCardTransactionByLiabilityType,
    canDeleteTransaction,
    canEditFieldOfMoneyRequest,
    canHoldUnholdReportAction,
    canUserPerformWriteAction as canUserPerformWriteActionReportUtils,
    getReportOrDraftReport,
    isInvoiceReport,
    isMoneyRequestReport as isMoneyRequestReportUtils,
    isTrackExpenseReport,
} from '@libs/ReportUtils';
import type {IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report, ReportAction, Session, Transaction} from '@src/types/onyx';
import useDeleteTransactions from './useDeleteTransactions';
import useDuplicateTransactionsAndViolations from './useDuplicateTransactionsAndViolations';
import {useMemoizedLazyExpensifyIcons} from './useLazyAsset';
import useLocalize from './useLocalize';
import useNetworkWithOfflineStatus from './useNetworkWithOfflineStatus';
import useOnyx from './useOnyx';
import useReportIsArchived from './useReportIsArchived';

// We do not use PRIMARY_REPORT_ACTIONS or SECONDARY_REPORT_ACTIONS because they weren't meant to be used in this situation. `value` property of returned options is later ignored.
const HOLD = 'HOLD';
const UNHOLD = 'UNHOLD';
const MOVE = 'MOVE';
const MERGE = 'MERGE';

function useSelectedTransactionsActions({
    report,
    reportActions,
    allTransactionsLength,
    session,
    onExportFailed,
    onExportOffline,
    policy,
    beginExportWithTemplate,
}: {
    report?: Report;
    reportActions: ReportAction[];
    allTransactionsLength: number;
    session?: Session;
    onExportFailed?: () => void;
    onExportOffline?: () => void;
    policy?: Policy;
    beginExportWithTemplate: (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => void;
}) {
    const {isOffline} = useNetworkWithOfflineStatus();
    const {selectedTransactionIDs, clearSelectedTransactions, currentSearchHash, selectedTransactions: selectedTransactionsMeta} = useSearchContext();
    const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false});
    const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
    const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH, {canBeMissing: true});
    const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES, {canBeMissing: true});
    const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS, {canBeMissing: true});
    const expensifyIcons = useMemoizedLazyExpensifyIcons(['Stopwatch', 'Trashcan', 'ArrowRight', 'Table', 'DocumentMerge', 'Export', 'ArrowCollapse'] as const);
    const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(selectedTransactionIDs);
    const isReportArchived = useReportIsArchived(report?.reportID);
    const {deleteTransactions} = useDeleteTransactions({report, reportActions, policy});
    const selectedTransactionsList = useMemo(
        () =>
            selectedTransactionIDs.reduce((acc, transactionID) => {
                const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
                if (transaction) {
                    acc.push(transaction);
                }
                return acc;
            }, [] as Transaction[]),
        [allTransactions, selectedTransactionIDs],
    );
    const hasTransactionsFromMultipleOwners = useMemo(() => {
        const knownOwnerIDs = new Set<number>();
        let hasUnknownOwner = false;

        for (const selectedTransactionInfo of Object.values(selectedTransactionsMeta ?? {})) {
            const ownerAccountID = selectedTransactionInfo?.ownerAccountID;
            if (typeof ownerAccountID === 'number') {
                knownOwnerIDs.add(ownerAccountID);
                if (knownOwnerIDs.size > 1) {
                    return true;
                }
            } else {
                hasUnknownOwner = true;
            }
        }

        for (const selectedTransaction of selectedTransactionsList) {
            const reportID = selectedTransaction?.reportID;
            if (!reportID || reportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
                hasUnknownOwner = true;
                continue;
            }

            const parentReport = getReportOrDraftReport(reportID);
            const ownerAccountID = parentReport?.ownerAccountID;

            if (typeof ownerAccountID === 'number') {
                knownOwnerIDs.add(ownerAccountID);
                if (knownOwnerIDs.size > 1) {
                    return true;
                }
            } else {
                hasUnknownOwner = true;
            }
        }

        if (hasUnknownOwner) {
            return knownOwnerIDs.size > 0 || selectedTransactionIDs.length > 1;
        }

        return false;
    }, [selectedTransactionsList, selectedTransactionsMeta, selectedTransactionIDs.length]);

    const {translate} = useLocalize();
    const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
    const isTrackExpenseThread = isTrackExpenseReport(report);
    const isInvoice = isInvoiceReport(report);

    let iouType: IOUType = CONST.IOU.TYPE.SUBMIT;

    if (isTrackExpenseThread) {
        iouType = CONST.IOU.TYPE.TRACK;
    }
    if (isInvoice) {
        iouType = CONST.IOU.TYPE.INVOICE;
    }

    const handleDeleteTransactions = useCallback(() => {
        const deletedThreadReportIDs = deleteTransactions(selectedTransactionIDs, duplicateTransactions, duplicateTransactionViolations, currentSearchHash, false);
        clearSelectedTransactions(true);
        setIsDeleteModalVisible(false);
        Navigation.removeReportScreen(new Set(deletedThreadReportIDs));
    }, [deleteTransactions, selectedTransactionIDs, duplicateTransactions, duplicateTransactionViolations, currentSearchHash, clearSelectedTransactions]);

    const showDeleteModal = useCallback(() => {
        setIsDeleteModalVisible(true);
    }, []);

    const hideDeleteModal = useCallback(() => {
        setIsDeleteModalVisible(false);
    }, []);

    const computedOptions = useMemo(() => {
        if (!selectedTransactionIDs.length) {
            return [];
        }
        const options = [];
        const isMoneyRequestReport = isMoneyRequestReportUtils(report);
        const isReportReimbursed = report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && report?.statusNum === CONST.REPORT.STATUS_NUM.REIMBURSED;

        let canHoldTransactions = selectedTransactionsList.length > 0 && isMoneyRequestReport && !isReportReimbursed;
        let canUnholdTransactions = selectedTransactionsList.length > 0 && isMoneyRequestReport;

        for (const selectedTransaction of selectedTransactionsList) {
            if (!canHoldTransactions && !canUnholdTransactions) {
                continue;
            }

            if (!selectedTransaction?.transactionID) {
                canHoldTransactions = false;
                canUnholdTransactions = false;
                continue;
            }
            const iouReportAction = getIOUActionForTransactionID(reportActions, selectedTransaction.transactionID);
            const holdReportAction = getReportAction(iouReportAction?.childReportID, `${selectedTransaction?.comment?.hold ?? ''}`);
            const {canHoldRequest, canUnholdRequest} = canHoldUnholdReportAction(report, iouReportAction, holdReportAction, selectedTransaction, policy);

            canHoldTransactions = canHoldTransactions && canHoldRequest;
            canUnholdTransactions = canUnholdTransactions && canUnholdRequest;
        }

        if (canHoldTransactions) {
            options.push({
                text: translate('iou.hold'),
                icon: expensifyIcons.Stopwatch,
                value: HOLD,
                onSelected: () => {
                    if (!report?.reportID) {
                        return;
                    }
                    Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: report.reportID}));
                },
            });
        }

        if (canUnholdTransactions) {
            options.push({
                text: translate('iou.unhold'),
                icon: expensifyIcons.Stopwatch,
                value: UNHOLD,
                onSelected: () => {
                    for (const transactionID of selectedTransactionIDs) {
                        const action = getIOUActionForTransactionID(reportActions, transactionID);
                        if (!action?.childReportID) {
                            continue;
                        }
                        unholdRequest(transactionID, action?.childReportID);
                    }
                    clearSelectedTransactions(true);
                },
            });
        }

        // Gets the list of options for the export sub-menu
        const getExportOptions = (): PopoverMenuItem[] => {
            // We provide the basic and expense level export options by default
            const exportOptions: PopoverMenuItem[] = [
                {
                    text: translate('export.basicExport'),
                    icon: expensifyIcons.Table,
                    onSelected: () => {
                        if (!report) {
                            return;
                        }
                        if (isOffline) {
                            onExportOffline?.();
                            return;
                        }
                        exportReportToCSV({reportID: report.reportID, transactionIDList: selectedTransactionIDs}, () => {
                            onExportFailed?.();
                        });
                        clearSelectedTransactions(true);
                    },
                },
            ];

            // If we've selected all the transactions on the report, we can also provide the report level export option
            const includeReportLevelExport = allTransactionsLength === selectedTransactionIDs.length;

            // If the user has any custom integration export templates, add them as export options
            const exportTemplates = getExportTemplates(integrationsExportTemplates ?? [], csvExportLayouts ?? {}, translate, policy, includeReportLevelExport);
            for (const template of exportTemplates) {
                exportOptions.push({
                    text: template.name,
                    icon: expensifyIcons.Table,
                    description: template.description,
                    onSelected: () => beginExportWithTemplate(template.templateName, template.type, selectedTransactionIDs, template.policyID),
                });
            }
            return exportOptions;
        };

        options.push({
            value: CONST.REPORT.SECONDARY_ACTIONS.EXPORT,
            text: translate('common.export'),
            backButtonText: translate('common.export'),
            icon: expensifyIcons.Export,
            rightIcon: expensifyIcons.ArrowRight,
            subMenuItems: getExportOptions(),
        });

        const canSelectedExpensesBeMoved = selectedTransactionsList.every((transaction) => {
            if (!transaction) {
                return false;
            }
            const iouReportAction = getIOUActionForTransactionID(reportActions, transaction.transactionID);

            const canMoveExpense = canEditFieldOfMoneyRequest(iouReportAction, CONST.EDIT_REQUEST_FIELD.REPORT, undefined, undefined, outstandingReportsByPolicyID);
            return canMoveExpense;
        });

        const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isReportArchived);
        if (canSelectedExpensesBeMoved && canUserPerformWriteAction && !hasTransactionsFromMultipleOwners) {
            options.push({
                text: translate('iou.moveExpenses', {count: selectedTransactionIDs.length}),
                icon: expensifyIcons.DocumentMerge,
                value: MOVE,
                onSelected: () => {
                    const shouldTurnOffSelectionMode = allTransactionsLength - selectedTransactionIDs.length <= 1;
                    const route = ROUTES.MONEY_REQUEST_EDIT_REPORT.getRoute(CONST.IOU.ACTION.EDIT, iouType, report?.reportID, shouldTurnOffSelectionMode, lastVisitedPath);
                    Navigation.navigate(route);
                },
            });
        }

        // In phase 1, we only show merge action if report is eligible for merge and only one transaction is selected
        const canMergeTransaction = selectedTransactionsList.length === 1 && report && isMergeAction(report, selectedTransactionsList, policy);
        if (canMergeTransaction) {
            options.push({
                text: translate('common.merge'),
                icon: expensifyIcons.ArrowCollapse,
                value: MERGE,
                onSelected: () => {
                    const targetTransaction = selectedTransactionsList.at(0);

                    if (!report || !targetTransaction) {
                        return;
                    }

                    setupMergeTransactionData(targetTransaction.transactionID, {targetTransactionID: targetTransaction.transactionID});
                    Navigation.navigate(ROUTES.MERGE_TRANSACTION_LIST_PAGE.getRoute(targetTransaction.transactionID, Navigation.getActiveRoute()));
                },
            });
        }

        const canAllSelectedTransactionsBeRemoved = selectedTransactionsList.every((transaction) => {
            const canRemoveTransaction = canDeleteCardTransactionByLiabilityType(transaction);
            const action = getIOUActionForTransactionID(reportActions, transaction.transactionID);
            const isActionDeleted = isDeletedAction(action);
            const isIOUActionOwner = typeof action?.actorAccountID === 'number' && typeof session?.accountID === 'number' && action.actorAccountID === session?.accountID;

            return canRemoveTransaction && isIOUActionOwner && !isActionDeleted;
        });

        const canRemoveReportTransaction = canDeleteTransaction(report, isReportArchived);

        if (canRemoveReportTransaction && canAllSelectedTransactionsBeRemoved) {
            options.push({
                text: translate('common.delete'),
                icon: expensifyIcons.Trashcan,
                value: CONST.REPORT.SECONDARY_ACTIONS.DELETE,
                onSelected: showDeleteModal,
            });
        }
        return options;
    }, [
        selectedTransactionIDs,
        report,
        selectedTransactionsList,
        translate,
        isReportArchived,
        policy,
        reportActions,
        clearSelectedTransactions,
        allTransactionsLength,
        integrationsExportTemplates,
        csvExportLayouts,
        isOffline,
        onExportOffline,
        onExportFailed,
        beginExportWithTemplate,
        outstandingReportsByPolicyID,
        iouType,
        lastVisitedPath,
        session?.accountID,
        showDeleteModal,
        hasTransactionsFromMultipleOwners,
        expensifyIcons,
    ]);

    return {
        options: computedOptions,
        handleDeleteTransactions,
        isDeleteModalVisible,
        showDeleteModal,
        hideDeleteModal,
    };
}

export default useSelectedTransactionsActions;
